#include "CentralTabDragProxy.h"
#include "CentralTabBar.h"
#include "MainWindow.h"
#include "Tabs/CentralTab.h"

#include "EventHelper.h"
#include "SystemHelper.h"

#include "Namespace.h"

#include <QApplication>
#include <QDesktopWidget>
#include <QDrag>
#include <QMimeData>

CentralTabDragProxy::CentralTabDragProxy(QObject *parent)
    : BaseDragProxy(parent), m_tabs(nullptr), m_window(nullptr) {
}

CentralTabDragProxy::CentralTabDragProxy(CentralTabWidget *tabs, QObject *parent)
    : CentralTabDragProxy(parent) {
    setTabWidget(tabs);
}

CentralTabDragProxy::~CentralTabDragProxy() {
}

CentralTabWidget *CentralTabDragProxy::tabWidget() const {
    return m_tabs;
}

void CentralTabDragProxy::setTabWidget(CentralTabWidget *tabWidget) {
    // Should never happen
    if (m_tabs) {
        m_tabs->removeEventFilter(this);
        disconnect(m_tabs->tabBar(), &CentralTabBar::dragStarted, this,
                   &CentralTabDragProxy::handleTabDragPrepare);
    }

    m_tabs = tabWidget;
    m_window = qobject_cast<MainWindow *>(m_tabs->window());
    Q_ASSERT(m_window);

    m_tabs->installEventFilter(this);

    connect(m_tabs->tabBar(), &CentralTabBar::dragStarted, this,
            &CentralTabDragProxy::handleTabDragPrepare);
}

bool CentralTabDragProxy::eventFilter(QObject *obj, QEvent *event) {
    if (obj == m_tabs) {
        switch (event->type()) {
        case QEvent::DragEnter:
        case QEvent::DragMove:
        case QEvent::Drop: {
            QDropEvent *e = static_cast<QDropEvent *>(event);
            const QMimeData *mime = e->mimeData();
            if (Ev::isValidDragMime(this, mime)) {
                if (mime->hasFormat(My::MIME_DRAG_ID)) {
                    QString idStr = QString::fromUtf8(mime->data(My::MIME_DRAG_ID));
                    bool isNum;
                    int id = idStr.toInt(&isNum);
                    if (isNum) {
                        CentralTab *tab = CentralTab::LookUp(id);
                        if (tab) {
                            int index = m_tabs->indexOf(tab);
                            if (index < 0) {
                                if (event->type() == QEvent::Drop) {
                                    tab->tabWidget()->removeTab(tab); // Remove from origin
                                    handleTabDragIn(tab);             // New to add
                                }
                                e->acceptProposedAction();
                            } else {
                                e->acceptProposedAction();
                            }
                        } else {
                            qDebug() << "Current process cannot recognize this tab!";
                        }
                    }
                }
            }
            if (e->isAccepted()) {
                return true;
            }
            break;
        }
        default:
            break;
        }
    }
    return ModelHandler::eventFilter(obj, event);
}

void CentralTabDragProxy::handleTabDragPrepare(int index, const QPoint &pos,
                                               const QPixmap &pixmap) {
    QDrag *drag = new QDrag(this);
    CentralTab *tab = m_tabs->tabAt(index);
    QMimeData *mime = Ev::createDragMime(this, tab->id());

    if (tab->carryMime()) {
        mime->setData("text/plain", tab->mimeText()); // Carry filename or tabname
    }

    drag->setMimeData(mime);
    drag->setPixmap(pixmap);
    drag->setHotSpot(pos);

    int cnt = m_tabs->count();

    handleTabDragStart(index); // If single then hide

    // Block
    Qt::DropAction res = drag->exec(Qt::ActionMask);
    // After Drop
    if (res == Qt::IgnoreAction) {
        handleTabDragBlank(index, pos); // New window in need
    }

    // Avoid being eaten by other application
    handleTabDragFinish(index, pos, m_tabs->count() != cnt);
}

void CentralTabDragProxy::handleTabDragStart(int index) {
    Q_UNUSED(index)
    if (m_tabs->count() == 1) {
        m_window->hide(); // Hide the window for convenience
    }
}

void CentralTabDragProxy::handleTabDragFinish(int index, const QPoint &pos, bool change) {
    Q_UNUSED(index)
    if (m_tabs->count() > 0) {
        if (!m_window->isVisible()) {
            m_window->showForward(); // Show the window again
            QPoint targetPos = Sys::fixDesktopPos(
                QCursor::pos() - m_tabs->mapTo(m_window, QPoint(0, 0)) - pos, m_window->size());
            m_window->move(targetPos);
        } else if (!change) {
            QTabBar *bar = m_tabs->tabBar();
            CentralTab *tab = m_tabs->tabAt(index);
            QRect rect = bar->tabRect(index);
            QPoint p = bar->mapFromGlobal(QCursor::pos());
            int newIndex = -1;
            if (p.x() < rect.left() || p.x() > rect.right()) {
                newIndex = bar->tabAt(p);
                if (newIndex < 0 && (p.y() >= 0 && p.y() <= bar->height())) {
                    if (p.x() < 0) {
                        newIndex = 0;
                    } else if (p.x() > bar->width()) {
                        newIndex = bar->count();
                    }
                }
            }
            if (newIndex >= 0) {
                m_tabs->removeTab(index);
                if (newIndex > m_tabs->count()) {
                    m_tabs->addTab(tab);
                } else {
                    m_tabs->insertTab(newIndex, tab);
                }
                m_tabs->setCurrentWidget(tab);
            }
            m_window->showForward();
        }
    } else if (m_tabs->count() == 0) {
        m_window->close();
    }
}

void CentralTabDragProxy::handleTabDragBlank(int index, const QPoint &pos) {
    CentralTab *tab = m_tabs->tabAt(index);
    m_tabs->removeTab(index);

    MainWindow *w = new MainWindow({tab});
    w->setWindowState(w->windowState() & ~Qt::WindowMaximized & Qt::WindowFullScreen);
    if (!m_window->isMaximized()) {
        w->resize(m_window->size());
    } else {
        w->resize(QApplication::desktop()->size() * 2.0 / 3.0);
    }
    w->showForward();

    QPoint targetPos = Sys::fixDesktopPos(
        QCursor::pos() - w->centralTabWidget()->mapTo(w, QPoint(0, 0)) - pos, w->size());
    w->move(targetPos);
}

void CentralTabDragProxy::handleTabDragIn(CentralTab *tab) {
    QTabBar *bar = m_tabs->tabBar();
    QPoint p = bar->mapFromGlobal(QCursor::pos());
    int index = bar->tabAt(p);
    if (index < 0 && (p.y() >= 0 && p.y() <= bar->height())) {
        if (p.x() < 0) {
            index = 0;
        } else if (p.x() > bar->width()) {
            index = bar->count();
        }
    }
    if (index < 0) {
        m_tabs->addTab(tab);
    } else {
        m_tabs->insertTab(index, tab);
    }
    m_window->showForward();
    m_tabs->setCurrentWidget(tab);
}
